/*
 * \brief  Hardware-resource access
 * \author Christian Helmuth
 * \date   2008-10-21
 */

/*
 * Copyright (C) 2008-2009 Christian Helmuth
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/lock.h>
#include <base/stdint.h>
#include <util/avl_tree.h>

#include <io_port_session/connection.h>
#include <io_mem_session/connection.h>

#include <dataspace/client.h>

extern "C" {
#include <dde_kit/resources.h>
}

using namespace Genode;


/***************
 ** I/O ports **
 ***************/

class Port_range : public Avl_node<Port_range>
{
	public:

		class Not_found : public Exception { };

	private:

		addr_t             _base;
		size_t             _size;

		Io_port_connection _port;

	public:

		Port_range(addr_t base, size_t size);
		~Port_range();

		/** AVL node comparison */
		bool higher(Port_range *range) {
			return (_base + _size <= range->_base); }

		/** AVL node lookup */
		Port_range *lookup(addr_t addr, size_t size)
		{
			if (addr >= _base &&
			    addr + size - 1 < _base + _size)
				return this;

			Port_range *r = child(_base + _size <= addr);

			if (!r) throw Not_found();

			return r->lookup(addr, size);
		}

		Io_port_connection *port() { return &_port; }
};


class Port_database : Avl_tree<Port_range>
{
	private:

		Lock _lock;

	public:

		Port_range *lookup(addr_t addr, size_t size)
		{
			Lock::Guard lock_guard(_lock);

			if (!first()) throw Port_range::Not_found();

			return first()->lookup(addr, size);
		}

		void insert(Port_range *range)
		{
			Lock::Guard lock_guard(_lock);

			Avl_tree<Port_range>::insert(range);
		}

		void remove(Port_range *range)
		{
			Lock::Guard lock_guard(_lock);

			range->remove();
		}
};


static Port_database *ports()
{
	static Port_database _ports;
	return &_ports;
}


Port_range::Port_range(addr_t base, size_t size)
: _base(base), _size(size), _port(base, size) {
	ports()->insert(this); }


Port_range::~Port_range() {
	ports()->remove(this); }


extern "C" int dde_kit_request_io(dde_kit_addr_t addr, dde_kit_addr_t size)
{
	try {
		new (env()->heap()) Port_range(addr, size);

		return 0;
	} catch (...) {
		return -1;
	}
}


extern "C" int dde_kit_release_io(dde_kit_addr_t addr, dde_kit_addr_t size)
{
	try {
		destroy(env()->heap(), ports()->lookup(addr, size));

		return 0;
	} catch (...) {
		return -1;
	}
}


extern "C" unsigned char dde_kit_inb(dde_kit_addr_t port)
{
	try {
		return ports()->lookup(port, 1)->port()->inb(port);
	} catch (...) {
		return 0;
	}
}


extern "C" unsigned short dde_kit_inw(dde_kit_addr_t port)
{
	try {
		return ports()->lookup(port, 2)->port()->inw(port);
	} catch (...) {
		return 0;
	}
}


extern "C" unsigned long dde_kit_inl(dde_kit_addr_t port)
{
	try {
		return ports()->lookup(port, 4)->port()->inl(port);
	} catch (...) {
		return 0;
	}
}


extern "C" void dde_kit_outb(dde_kit_addr_t port, unsigned char val)
{
	try {
		ports()->lookup(port, 1)->port()->outb(port, val);
	} catch (...) { }
}


extern "C" void dde_kit_outw(dde_kit_addr_t port, unsigned short val)
{
	try {
		ports()->lookup(port, 2)->port()->outw(port, val);
	} catch (...) { }
}


extern "C" void dde_kit_outl(dde_kit_addr_t port, unsigned long val)
{
	try {
		ports()->lookup(port, 4)->port()->outl(port, val);
	} catch (...) { }
}


/******************
 ** MMIO regions **
 ******************/

class Mem_range : public Avl_node<Mem_range>
{
	public:

		class Not_found : public Exception { };
		class Resource_not_accessible : public Exception { };

	private:

		addr_t                      _base;
		size_t                      _size;

		Io_mem_connection           _mem;
		Io_mem_dataspace_capability _ds;

		addr_t                      _vaddr;

	public:

		Mem_range(addr_t base, size_t size);
		~Mem_range();

		/** AVL node comparison */
		bool higher(Mem_range *range) {
			return (_base + _size <= range->_base); }

		/** AVL node lookup */
		Mem_range *lookup(addr_t addr, size_t size)
		{
			if (addr >= _base &&
			    addr + size - 1 < _base + _size)
				return this;

			Mem_range *r = child(_base + _size <= addr);

			if (!r) throw Not_found();

			return r->lookup(addr, size);
		}

		Io_mem_connection *mem() { return &_mem; }
		addr_t vaddr() const     { return _vaddr; }
};


class Mem_database : Avl_tree<Mem_range>
{
	private:

		Lock _lock;

	public:

		Mem_range *lookup(addr_t addr, size_t size)
		{
			Lock::Guard lock_guard(_lock);

			if (!first()) throw Mem_range::Not_found();

			return first()->lookup(addr, size);
		}

		void insert(Mem_range *range)
		{
			Lock::Guard lock_guard(_lock);

			Avl_tree<Mem_range>::insert(range);
		}

		void remove(Mem_range *range)
		{
			Lock::Guard lock_guard(_lock);

			range->remove();
		}
};


static Mem_database *mem_db()
{
	static Mem_database _mem_db;
	return &_mem_db;
}


Mem_range::Mem_range(addr_t base, size_t size)
: _base(base), _size(size), _mem(base, size), _ds(_mem.dataspace())
{
	if (!_ds.valid()) throw Resource_not_accessible();

	_vaddr = (addr_t) env()->rm_session()->attach(_ds);

	mem_db()->insert(this);
}


Mem_range::~Mem_range() {
	mem_db()->remove(this); }


extern "C" int dde_kit_request_mem(dde_kit_addr_t addr, dde_kit_size_t size, dde_kit_addr_t *vaddr)
{
	try {
		*vaddr = ((new (env()->heap()) Mem_range(addr, size))->vaddr());

		return 0;
	} catch (...) {
		return -1;
	}
}


extern "C" int dde_kit_release_mem(dde_kit_addr_t addr, dde_kit_size_t size)
{
	try {
		destroy(env()->heap(), mem_db()->lookup(addr, size));

		return 0;
	} catch (...) {
		return -1;
	}
}
